#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import sys
reload(sys)
sys.setdefaultencoding('UTF-8')
import re
import cgi
import utility
try:
    import xml.etree.ElementTree as ET
except:
    import cElementTree as ET


""" 默认设置参数 """
_default_set = {
    'encoding': 'utf-8',            # XML编码
    'unescape': False,              # 是否转换HTML实体
    'strip_root': True,             # 是否去除解析根节点
    'header_declare': False,        # 是否XML头部声明
    'xml_version': '1.0',           # XML版本号
    'xml_cdata': True,              # 是否使用XML CDATA
    'trim': True,                   # 是否去除空白字符
    'indent': None,                 # XML层次缩进
    'sort_flag': 'ASC',             # 标签排序标识 ASC: 正序 DESC:倒序 False: 不排序
    'sort_keys': ['ASC', 'DESC'],   # 标签排序标识列表 ASC: 正序 DESC:倒序
    'has_attr': False,              # 是否包含属性
    'attr_key': 'attr',             # 标签属性标识key
    'value_key': 'value'            # 标签值标识key
}

class LazyXML(object):
    def __init__(self, **kw):
        self.regex = {
            'xml_ns': re.compile(r"\{(.*)\}(.*)"),    # XML命名空间正则匹配编译对象
            'xml_header': re.compile(r'<\?xml\s+[^>]*\?>', re.I)  # XML头部正则匹配编译对象 不区分大小写
        }
        self.setting(**kw)

    def setting(self, **kw):
        for k,v in _default_set.iteritems():
            setattr(self, k , kw[k] if k in kw else v)

    def xml2dict(self, content):
        """"""""""""""""""""""""""""""""""""""""""""""""
         # XML转化为字典
         #
         # @author  lazyboy <lazyboy.fan@gmail.com>
         # @param   str content     XML内容
         # @return  dict
         # @todo
        """"""""""""""""""""""""""""""""""""""""""""""""
        content = self.xml_filter(content)
        el = ET.fromstring(content)
        tree = self.parse(el)
        return tree[1] if self.strip_root else {tree[0]:tree[1]}

    def xml_filter(self, content):
        content = utility.strip_whitespace(content, True) if self.trim else content.strip()
        if self.encoding.lower() != _default_set['encoding']:
            """ 非默认编码 作编码转换并去除xml头部 """
            conv_content = content.decode(self.encoding)
            content = self.strip_xml_header(conv_content)
        if self.unescape:
            content = utility.html_entity_decode(content)
        return content

    def strip_xml_header(self, content):
        return self.regex['xml_header'].sub('', content)

    def parse(self, el):
        """"""""""""""""""""""""""""""""""""""""""""""""
         # 解析XML文档节点信息
         #
         # @author  lazyboy <lazyboy.fan@gmail.com>
         # @param   object el       XML文档Element对象
         # @return  tuple
         # @todo
        """"""""""""""""""""""""""""""""""""""""""""""""
        tree = {}
        node = self.get_node(el)
        children = el.getchildren() if sys.version_info < (2,7) else list(el)
        # 是否有子节点
        if children:
            for item in children:
                tag,value = self.parse(item)
                # 子节点首次出现在父节点中
                if tag not in tree:
                    tree[tag] = value
                else:
                    # 子节点已在父节点中存在,则应转成list结构
                    if not isinstance(tree[tag],list):
                        tree[tag] = [tree.pop(tag)]
                    tree[tag].append(value)
            return (node['tag'],tree)
        else:
            return (node['tag'],node['value'])

    def get_node(self, el):
        """"""""""""""""""""""""""""""""""""""""""""""""
         # XML节点信息
         #
         # @author  lazyboy <lazyboy.fan@gmail.com>
         # @param   object el
         # @return  dict
         # @todo
        """"""""""""""""""""""""""""""""""""""""""""""""
        ns,tag = self.split_namespace(el.tag)
        return {'tag':tag, 'value':el.text or '', 'attr':el.attrib, 'namespace':ns}

    def split_namespace(self, tag):
        """"""""""""""""""""""""""""""""""""""""""""""""
         # 分割节点tag中的命名空间
         #
         # @author  lazyboy <lazyboy.fan@gmail.com>
         # @param   str tag 节点标签
         # @return  tuple (namespace,tag)
         # @todo
        """"""""""""""""""""""""""""""""""""""""""""""""
        result = self.regex['xml_ns'].search(tag)
        return result.groups() if result else ('', tag)

    def dict2xml(self, data):
        """"""""""""""""""""""""""""""""""""""""""""""""
         # 字典数据转成XML文档
         #
         # @author  lazyboy <lazyboy.fan@gmail.com>
         # @param   dict data
         # @return  str
         # @todo
        """"""""""""""""""""""""""""""""""""""""""""""""
        xml = [self.build_xml_header()] if self.header_declare else []
        self.build_tree(data, xml)
        return ''.join(xml).strip()

    def build_xml_header(self):
        return '<?xml version="%s" encoding="%s"?>' % (self.xml_version, self.encoding)

    def create_tree(self,data,key=None):
        """"""""""""""""""""""""""""""""""""""""""""""""
         # 创建XML文档树 (废弃方法 不建议使用)
         #
         # @author  lazyboy <lazyboy.fan@gmail.com>
         # @param   mixed data
         # @param   str key 标签名  默认：None
         # @return  str xml
         # @todo
        """"""""""""""""""""""""""""""""""""""""""""""""
        xml = ''
        if isinstance(data,dict):
            for k,v in data.iteritems():
                if isinstance(v,(list,tuple)):
                    xml += self.create_tree(v,k)
                else:
                    xml += self.build_tag(k,self.create_tree(v))
        elif isinstance(data,(list,tuple)):
            for v in data:
                xml += self.build_tag(key,self.create_tree(v))
        elif isinstance(data,(int,long,float)):
            xml += str(data)
        elif getattr(data,'__dict__',None):
            xml += self.create_tree(vars(data))
        else:
            xml += ('<![CDATA[%s]]>' % data) if data else ''
        return xml

    def build_tree(self, data, tree=[], depth=0):
        """
        构建节点树(包含节点属性)
        @author  lazyboy <lazyboy.fan@gmail.com>
        @param
        @return  void
        @todo
        """
        indent = ('\n%s' % (self.indent * depth)) if self.indent else ''
        if isinstance(data, dict):
            if self.sort_flag in self.sort_keys:
                flag = (self.sort_flag == 'DESC')
                iter = sorted(data.iteritems(), key=lambda x:x[0], reverse=flag)
            else:
                iter = data.iteritems()
            for k,v in iter:
                attrs = {}
                if isinstance(v, (list, tuple)):
                    for item in v:
                        tree.append('%s%s' % (indent, self.tag_start(k, attrs)))
                        self.build_tree(item, tree, depth+1)
                        tree.append('%s%s' % (indent, self.tag_end(k)))
                    continue

                """ 存在子节点并含有属性,取本节点属性值 """
                if self.has_attr and isinstance(v, dict):
                    attrs = v.get(self.attr_key) or {}
                    v = v.get(self.value_key) or ''

                tree.append('%s%s' % (indent, self.tag_start(k, attrs)))
                self.build_tree(v, tree, depth+1)
                """ 根据标签是否有子节点,缩进结束标签 """
                eif = (indent if isinstance(v, (dict, list, tuple)) else '', self.tag_end(k))
                tree.append('%s%s' % eif)
        elif isinstance(data, (int, long, float)):
            tree.append(str(data))
        else:
            data = data or ''
            format = ('<![CDATA[%s]]>' % data) if self.xml_cdata else ('%s' % cgi.escape(data))
            tree.append(format)
        return tree

    def build_tag(self, tag, text='', attr={}):
        """"""""""""""""""""""""""""""""""""""""""""""""
         # 生成单个标签结构
         #
         # @author  lazyboy <lazyboy.fan@gmail.com>
         # @param   str tag 标签名
         # @param   str text 标签值
         # @return  str
         # @todo
        """"""""""""""""""""""""""""""""""""""""""""""""
        if attr:
            format = '<%s %s>%s</%s>' % (tag, self.build_attr(attr), text, tag)
        else:
            format = '<%s>%s</%s>' % (tag, text, tag)
        return format

    def tag_start(self, tag, attr={}):
        return '<%s %s>' % (tag, self.build_attr(attr)) if attr else '<%s>' % tag

    def tag_end(self, tag):
        return '</%s>' % tag

    def build_attr(self, attr):
        return ' '.join(map(lambda x:('%s="%s"' % x), attr.iteritems()))